package com.mashuai.utils.toolkit.core.msConverts;

import com.mashuai.utils.base.MsEmptyUtils;
import com.mashuai.utils.toolkit.achieve.MsExegesis;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @ClassName: TreeUtils
 * @Description: 树结构工具类
 */
@MsExegesis("树结构工具类")
public class MsTreeUtils {
    /**
     * 构建树效率 > 双for > 递归
     * 用法举例：buildTreeStructure(getData(), TreeDto::getId, TreeDto::getPid, TreeDto::setChildren, 0)
     *
     * @param data          数据
     * @param idCallback    ID
     * @param pidCallback   pid
     * @param childCallback 当前对象需要设置的子节点集合
     * @param rootId        根节点值。以其构建树，如果一个跟节点出现3次，那就构建集合长度为3的树
     * @return List<树>,如果无特殊需求，直接list.get(0)即可
     * @author mashuai
     */
    @MsExegesis("构建树效率 > 双for > 递归。childCallback@当前对象需要设置的子节点集合；rootId@根节点值。以其构建树，如果一个跟节点出现3次，那就构建集合长度为3的树")
    public static <S, T> List<T> buildTreeStructure(List<T> data, Function<T, S> idCallback, Function<T, S> pidCallback, BiConsumer<T, List<T>> childCallback, S rootId) {
        //根据pid分组
        Map<S, List<T>> pidMap = data.stream().collect(Collectors.groupingBy(t -> pidCallback.apply(t)));
        for (T t : data) {
            //获取当前节点的id值
            S id = idCallback.apply(t);
            List<T> ts = pidMap.get(id);
            //如果当前节点id作为其他子节点的pid，则把其他节点添加到该集合中去
            MsEmptyUtils.isCollectNotZero(ts, () -> childCallback.accept(t, ts));
        }
        //只返回顶层节点id所在的pid值
        return data.stream().filter(t -> String.valueOf(pidCallback.apply(t)).equals(String.valueOf(rootId))).collect(Collectors.toList());
    }

    /**
     * 获取所有叶子节点.效率远大于递归。节点越多越明显
     * 用法：TreeUtils.getAlChildNodes(getData(), TreeDtoInt::getId, TreeDtoInt::getPid, 6, true);
     *
     * @param dataList         所有节点数据
     * @param idCallback       id
     * @param pidCallback      pid
     * @param nodeId           当前需要查询的节点ID
     * @param isAddCurrentNode 是否把当前节点加入到结果中
     * @return
     * @author mashuai
     */
    @MsExegesis(" 获取所有叶子节点.效率远大于递归。节点越多越明显")
    public static <S, T> List<T> getAllChildNodes(List<T> dataList, Function<T, S> idCallback, Function<T, S> pidCallback, S nodeId, Boolean isAddCurrentNode) {
        if (dataList.size() == 0 || nodeId == null) {
            throw new RuntimeException("dataList无数据/nodeId为空");
        }
        List<T> resultList = new ArrayList<>();
        Map<S, List<T>> pidMap = dataList.stream().collect(Collectors.groupingBy(t -> pidCallback.apply(t)));
        //获取当前节点
        T m = dataList.stream().filter(t -> String.valueOf(idCallback.apply(t)).equals(String.valueOf(nodeId))).findFirst().get();
        if (isAddCurrentNode) {
            resultList.add(m);
        }
        Stack<T> stack = new Stack<>();
        stack.add(m);
        //思路：把要查询的node放进栈中，每次循环从pidMap取出以node.id为key的子节点集合childList。然后再把childList放进栈中继续遍历，直至全部出栈
        while (!stack.isEmpty()) {
            T node = stack.pop();
            List<T> childList = pidMap.get(idCallback.apply(node));
            if (childList != null && childList.size() != 0) {
                resultList.addAll(childList);
                stack.addAll(childList);
            }
        }
        return resultList;
    }

    /**
     * 获某个节点所在的路径（简单说根据节点遍历他所有的父节点）
     * 用法：TreeUtils.getNodePath(getData(), TreeDtoInt::getId, TreeDtoInt::getPid, 7, 0);
     *
     * @param dataList    数据
     * @param idCallback  id
     * @param pidCallback pid
     * @param nodeId      需要查询的节点
     * @param rootId      跟节点ID。作为终止条件
     * @return 集合的顺序，就是从最顶层节点依次到要寻找的节点一个顺序
     * @author mashuai
     */
    @MsExegesis(" 获某个节点所在的路径（简单说根据节点遍历他所有的父节点）")
    public static <S, T> List<T> getNodePath(List<T> dataList, Function<T, S> idCallback, Function<T, S> pidCallback, S nodeId, S rootId) {
        List<T> resultList = new ArrayList<>();
        //获取当前节点
        T m = dataList.stream().filter(t -> String.valueOf(idCallback.apply(t)).equals(String.valueOf(nodeId))).findFirst().get();
        Stack<T> stack = new Stack<>();
        resultList.add(m);
        stack.add(m);
        while (!stack.isEmpty()) {
            T t = stack.pop();
            S tPid = pidCallback.apply(t);
            S tId = idCallback.apply(t);
            if (tPid.equals(rootId)) {
                break;
            }
            for (T node : dataList) {
                if (tPid.equals(idCallback.apply(node))) {
                    resultList.add(node);
                    stack.add(node);
                    break;
                }
            }
        }
        Collections.reverse(resultList);
        return resultList;
    }
}
